package org.raftkv.raft.pb;


import org.raftkv.raft.pb.enums.MessageType;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * 在 entries 被写入持久化存储中以后，需要发送给节点的数据
 * <BR>
 * 字段是可选的，在 MessageType 中有说明
 *
 * @author wzy
 * @see MessageType
 */
public class Message {

    /**
     * 消息类型，每条消息中都存在的字段
     */
    private MessageType type;

    /**
     * 发送消息节点 id，每条消息中都存在的字段
     */
    private long to;

    /**
     * 接受消息节点 id，每条消息中都存在的字段
     */
    private long from;

    /**
     * 当前节点的任期
     */
    private long term;

    /**
     * 发送的日志所处的任期
     */
    private long logTerm;

    /**
     * 日志索引 id
     */
    private long index;

    /**
     * 日志条目数组
     */
    private List<Entry> entries;

    /**
     * 需要提交日志的 index
     */
    private long commit;

    /**
     * 快照数据
     */
    private Snapshot snapShot;

    /**
     * 是否拒绝
     */
    private boolean reject;

    /**
     * 拒绝同步日志请求时返回的当前节点日志ID，用于被拒绝方快速定位到下一次合适的同步日志位置
     */
    private long rejectHint;

    /**
     * 上下文数据
     */
    private byte[] context;


    public static MessageBuilder builder() {
        return new MessageBuilder();
    }

    public static boolean isResponseMsg(MessageType type) {
        return type == MessageType.MsgAppResp || type == MessageType.MsgVoteResp
                || type == MessageType.MsgHeartbeatResp || type == MessageType.MsgUnreachable;
    }

    public static boolean isLocalMsg(MessageType type) {
        return type == MessageType.MsgHup || type == MessageType.MsgBeat || type == MessageType.MsgUnreachable
                || type == MessageType.MsgSnapStatus || type == MessageType.MsgCheckQuorum;
    }

    public MessageType getType() {
        return type;
    }

    public void setType(MessageType type) {
        this.type = type;
    }

    public long getFrom() {
        return from;
    }

    public void setFrom(long from) {
        this.from = from;
    }

    public long getTo() {
        return to;
    }

    public void setTo(long to) {
        this.to = to;
    }

    public long getTerm() {
        return term;
    }

    public void setTerm(long term) {
        this.term = term;
    }

    public long getLogTerm() {
        return logTerm;
    }

    public void setLogTerm(long logTerm) {
        this.logTerm = logTerm;
    }

    public long getIndex() {
        return index;
    }

    public void setIndex(long index) {
        this.index = index;
    }

    public List<Entry> getEntries() {
        return entries;
    }

    public void setEntries(List<Entry> entries) {
        this.entries = entries;
    }

    public long getCommit() {
        return commit;
    }

    public void setCommit(long commit) {
        this.commit = commit;
    }

    public Snapshot getSnapShot() {
        return snapShot;
    }

    public void setSnapShot(Snapshot snapShot) {
        this.snapShot = snapShot;
    }

    public boolean isReject() {
        return reject;
    }

    public void setReject(boolean reject) {
        this.reject = reject;
    }

    public long getRejectHint() {
        return rejectHint;
    }

    public void setRejectHint(long rejectHint) {
        this.rejectHint = rejectHint;
    }

    public byte[] getContext() {
        return context;
    }

    public void setContext(byte[] context) {
        this.context = context;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Message message = (Message) o;
        return from == message.from &&
                to == message.to &&
                term == message.term &&
                logTerm == message.logTerm &&
                index == message.index &&
                commit == message.commit &&
                reject == message.reject &&
                rejectHint == message.rejectHint &&
                type == message.type &&
                Objects.equals(entries, message.entries) &&
                Objects.equals(snapShot, message.snapShot) &&
                Arrays.equals(context, message.context);
    }

    @Override
    public int hashCode() {
        int result = Objects.hash(type, from, to, term, logTerm, index, entries, commit, snapShot, reject, rejectHint);
        result = 31 * result + Arrays.hashCode(context);
        return result;
    }

    @Override
    public String toString() {
        return "Message{" +
                "type=" + type +
                ", from=" + from +
                ", to=" + to +
                ", term=" + term +
                ", logTerm=" + logTerm +
                ", index=" + index +
                ", entries=" + entries +
                ", commit=" + commit +
                ", snapShot=" + snapShot +
                ", reject=" + reject +
                ", rejectHint=" + rejectHint +
                ", context=" + Arrays.toString(context) +
                '}';
    }

    public static class MessageBuilder {

        private Message message = new Message();

        public MessageBuilder type(MessageType type) {
            message.type = type;
            return this;
        }

        public MessageBuilder from(long from) {
            message.from = from;
            return this;
        }

        public MessageBuilder to(long to) {
            message.to = to;
            return this;
        }

        public MessageBuilder snapshot(Snapshot snapshot) {
            message.snapShot = snapshot;
            return this;
        }

        public MessageBuilder reject(boolean reject) {
            message.reject = reject;
            return this;
        }

        public MessageBuilder commit(long commit) {
            message.commit = commit;
            return this;
        }

        public MessageBuilder context(byte[] context) {
            message.context = context;
            return this;
        }

        public Message build() {
            return message;
        }

        public MessageBuilder index(long index) {
            message.index = index;
            return this;
        }

        public MessageBuilder logTerm(long logTerm) {
            message.logTerm = logTerm;
            return this;
        }

        public MessageBuilder entries(List<Entry> entries) {
            message.entries = new ArrayList<>(entries);
            return this;
        }

        public MessageBuilder rejectHint(long rejectHint) {
            message.rejectHint = rejectHint;
            return this;
        }

        public MessageBuilder term(long term) {
            message.term = term;
            return this;
        }

    }

}